package org.andengine.extension.svg.util;

import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.Path.FillType;
import android.graphics.RectF;
import android.util.FloatMath;

import org.andengine.extension.svg.adt.SVGPaint;
import org.andengine.extension.svg.adt.SVGProperties;
import org.andengine.extension.svg.util.constants.ISVGConstants;
import org.andengine.util.math.MathUtils;

import java.util.LinkedList;
import java.util.Queue;


/**
 * Parses a single SVG path and returns it as a <code>android.graphics.Path</code> object.
 * An example path is <code>M250,150L150,350L350,350Z</code>, which draws a triangle.
 *
 * @author Nicolas Gramlich
 * @see <a href="http://www.w3.org/TR/SVG/paths.html">Specification</a>.
 * <p/>
 * (c) 2010 Nicolas Gramlich
 * (c) 2011 Zynga Inc.
 * @since 17:16:39 - 21.05.2011
 */
public class SVGPathParser implements ISVGConstants {
    // ===========================================================
    // Constants
    // ===========================================================

    // ===========================================================
    // Fields
    // ===========================================================

    private String mString;
    private int mLength;
    private int mPosition;
    private char mCurrentChar;

    private final PathParserHelper mPathParserHelper = new PathParserHelper();

    private Path mPath;
    private Character mCommand = null;
    private int mCommandStart = 0;
    private final Queue<Float> mCommandParameters = new LinkedList<Float>();

    private float mSubPathStartX;
    private float mSubPathStartY;
    private float mLastX;
    private float mLastY;
    private float mLastCubicBezierX2;
    private float mLastCubicBezierY2;
    private float mLastQuadraticBezierX2;
    private float mLastQuadraticBezierY2;

    private final RectF mArcRect = new RectF();

    // ===========================================================
    // Constructors
    // ===========================================================

    // ===========================================================
    // Getter & Setter
    // ===========================================================

    // ===========================================================
    // Methods for/from SuperClass/Interfaces
    // ===========================================================

    // ===========================================================
    // Methods
    // ===========================================================

    public void parse(final SVGProperties pSVGProperties, final Canvas pCanvas, final SVGPaint pSVGPaint) {
        final Path path = this.parse(pSVGProperties);

        final boolean fill = pSVGPaint.setFill(pSVGProperties);
        if (fill) {
            pCanvas.drawPath(path, pSVGPaint.getPaint());
        }

        final boolean stroke = pSVGPaint.setStroke(pSVGProperties);
        if (stroke) {
            pCanvas.drawPath(path, pSVGPaint.getPaint());
        }

        if (fill || stroke) {
            pSVGPaint.ensureComputedBoundsInclude(path);
        }
    }

    /**
     * Uppercase rules are absolute positions, lowercase are relative.
     * Types of path rules:
     * <p/>
     * <ol>
     * <li>M/m - (x y)+ - Move to (without drawing)
     * <li>Z/z - (no params) - Close path (back to starting point)
     * <li>L/l - (x y)+ - Line to
     * <li>H/h - x+ - Horizontal ine to
     * <li>V/v - y+ - Vertical line to
     * <li>C/c - (x1 y1 x2 y2 x y)+ - Cubic bezier to
     * <li>S/s - (x2 y2 x y)+ - Smooth cubic bezier to (shorthand that assumes the x2, y2 from previous C/S is the x1, y1 of this bezier)
     * <li>Q/q - (x1 y1 x y)+ - Quadratic bezier to
     * <li>T/t - (x y)+ - Smooth quadratic bezier to (assumes previous control point is "reflection" of last one w.r.t. to current point)
     * <li>A/a - ... - Arc to</li>
     * </ol>
     * <p/>
     * Numbers are separate by whitespace, comma or nothing at all (!) if they are self-delimiting, (ie. begin with a - sign)
     */
    private Path parse(final SVGProperties pSVGProperties) {
        final String pathString = pSVGProperties.getStringProperty(ATTRIBUTE_PATHDATA);
        if (pathString == null) {
            return null;
        }

        this.mString = pathString.trim();
        this.mLastX = 0;
        this.mLastY = 0;
        this.mLastCubicBezierX2 = 0;
        this.mLastCubicBezierY2 = 0;
        this.mCommand = null;
        this.mCommandParameters.clear();
        this.mPath = new Path();
        if (this.mString.length() == 0) {
            return this.mPath;
        }

        final String fillrule = pSVGProperties.getStringProperty(ATTRIBUTE_FILLRULE);
        if (fillrule != null) {
            if (ATTRIBUTE_FILLRULE_VALUE_EVENODD.equals(fillrule)) {
                this.mPath.setFillType(FillType.EVEN_ODD);
            } else {
                this.mPath.setFillType(FillType.WINDING);
            }

			/*
             *  TODO Check against:
			 *  http://www.w3.org/TR/SVG/images/painting/fillrule-nonzero.svg / http://www.w3.org/TR/SVG/images/painting/fillrule-nonzero.png
			 *  http://www.w3.org/TR/SVG/images/painting/fillrule-evenodd.svg / http://www.w3.org/TR/SVG/images/painting/fillrule-evenodd.png
			 */
        }

        this.mCurrentChar = this.mString.charAt(0);

        this.mPosition = 0;
        this.mLength = this.mString.length();
        while (this.mPosition < this.mLength) {
            try {
                this.mPathParserHelper.skipWhitespace();
                if (Character.isLetter(this.mCurrentChar) && (this.mCurrentChar != 'e') && (this.mCurrentChar != 'E')) {
                    this.processCommand();

                    this.mCommand = this.mCurrentChar;
                    this.mCommandStart = this.mPosition;
                    this.mPathParserHelper.advance();
                } else {
                    final float parameter = this.mPathParserHelper.nextFloat();
                    this.mCommandParameters.add(parameter);
                }
            } catch (final Throwable t) {
                throw new IllegalArgumentException("Error parsing: '" + this.mString.substring(this.mCommandStart, this.mPosition) + "'. Command: '" + this.mCommand + "'. Parameters: '" + this.mCommandParameters.size() + "'.", t);
            }
        }
        this.processCommand();
        return this.mPath;
    }

    private void processCommand() {
        if (this.mCommand != null) {
            // Process command
            this.generatePathElement();
            this.mCommandParameters.clear();
        }
    }

    private void generatePathElement() {
        boolean wasCubicBezierCurve = false;
        boolean wasQuadraticBezierCurve = false;
        switch (this.mCommand) { // TODO Extract to constants
            case 'm':
                this.generateMove(false);
                break;
            case 'M':
                this.generateMove(true);
                break;
            case 'l':
                this.generateLine(false);
                break;
            case 'L':
                this.generateLine(true);
                break;
            case 'h':
                this.generateHorizontalLine(false);
                break;
            case 'H':
                this.generateHorizontalLine(true);
                break;
            case 'v':
                this.generateVerticalLine(false);
                break;
            case 'V':
                this.generateVerticalLine(true);
                break;
            case 'c':
                this.generateCubicBezierCurve(false);
                wasCubicBezierCurve = true;
                break;
            case 'C':
                this.generateCubicBezierCurve(true);
                wasCubicBezierCurve = true;
                break;
            case 's':
                this.generateSmoothCubicBezierCurve(false);
                wasCubicBezierCurve = true;
                break;
            case 'S':
                this.generateSmoothCubicBezierCurve(true);
                wasCubicBezierCurve = true;
                break;
            case 'q':
                this.generateQuadraticBezierCurve(false);
                wasQuadraticBezierCurve = true;
                break;
            case 'Q':
                this.generateQuadraticBezierCurve(true);
                wasQuadraticBezierCurve = true;
                break;
            case 't':
                this.generateSmoothQuadraticBezierCurve(false);
                wasQuadraticBezierCurve = true;
                break;
            case 'T':
                this.generateSmoothQuadraticBezierCurve(true);
                wasQuadraticBezierCurve = true;
                break;
            case 'a':
                this.generateArc(false);
                break;
            case 'A':
                this.generateArc(true);
                break;
            case 'z':
            case 'Z':
                this.generateClose();
                break;
            default:
                throw new RuntimeException("Unexpected SVG command: " + this.mCommand);
        }
        if (!wasCubicBezierCurve) {
            this.mLastCubicBezierX2 = this.mLastX;
            this.mLastCubicBezierY2 = this.mLastY;
        }
        if (!wasQuadraticBezierCurve) {
            this.mLastQuadraticBezierX2 = this.mLastX;
            this.mLastQuadraticBezierY2 = this.mLastY;
        }
    }

    private void assertParameterCountMinimum(final int pParameterCount) {
        if (this.mCommandParameters.size() < pParameterCount) {
            throw new RuntimeException("Incorrect parameter count: '" + this.mCommandParameters.size() + "'. Expected at least: '" + pParameterCount + "'.");
        }
    }

    private void assertParameterCount(final int pParameterCount) {
        if (this.mCommandParameters.size() != pParameterCount) {
            throw new RuntimeException("Incorrect parameter count: '" + this.mCommandParameters.size() + "'. Expected: '" + pParameterCount + "'.");
        }
    }

    private void generateMove(final boolean pAbsolute) {
        this.assertParameterCountMinimum(2);
        final float x = this.mCommandParameters.poll();
        final float y = this.mCommandParameters.poll();
        /** Moves the line from mLastX,mLastY to x,y. */
        if (pAbsolute) {
            this.mPath.moveTo(x, y);
            this.mLastX = x;
            this.mLastY = y;
        } else {
            this.mPath.rMoveTo(x, y);
            this.mLastX += x;
            this.mLastY += y;
        }
        this.mSubPathStartX = this.mLastX;
        this.mSubPathStartY = this.mLastY;
        if (this.mCommandParameters.size() >= 2) {
            this.generateLine(pAbsolute);
        }
    }

    private void generateLine(final boolean pAbsolute) {
        this.assertParameterCountMinimum(2);
        /** Draws a line from mLastX,mLastY to x,y. */
        if (pAbsolute) {
            while (this.mCommandParameters.size() >= 2) {
                final float x = this.mCommandParameters.poll();
                final float y = this.mCommandParameters.poll();
                this.mPath.lineTo(x, y);
                this.mLastX = x;
                this.mLastY = y;
            }
        } else {
            while (this.mCommandParameters.size() >= 2) {
                final float x = this.mCommandParameters.poll();
                final float y = this.mCommandParameters.poll();
                this.mPath.rLineTo(x, y);
                this.mLastX += x;
                this.mLastY += y;
            }
        }
    }

    private void generateHorizontalLine(final boolean pAbsolute) {
        this.assertParameterCountMinimum(1);
        /** Draws a horizontal line to the point defined by mLastY and x. */
        if (pAbsolute) {
            while (this.mCommandParameters.size() >= 1) {
                final float x = this.mCommandParameters.poll();
                this.mPath.lineTo(x, this.mLastY);
                this.mLastX = x;
            }
        } else {
            while (this.mCommandParameters.size() >= 1) {
                final float x = this.mCommandParameters.poll();
                this.mPath.rLineTo(x, 0);
                this.mLastX += x;
            }
        }
    }

    private void generateVerticalLine(final boolean pAbsolute) {
        this.assertParameterCountMinimum(1);
        /** Draws a vertical line to the point defined by mLastX and y. */
        if (pAbsolute) {
            while (this.mCommandParameters.size() >= 1) {
                final float y = this.mCommandParameters.poll();
                this.mPath.lineTo(this.mLastX, y);
                this.mLastY = y;
            }
        } else {
            while (this.mCommandParameters.size() >= 1) {
                final float y = this.mCommandParameters.poll();
                this.mPath.rLineTo(0, y);
                this.mLastY += y;
            }
        }
    }

    private void generateCubicBezierCurve(final boolean pAbsolute) {
        this.assertParameterCountMinimum(6);
        /** Draws a cubic bezier curve from current pen point to x,y.
         * x1,y1 and x2,y2 are start and end control points of the curve. */
        if (pAbsolute) {
            while (this.mCommandParameters.size() >= 6) {
                final float x1 = this.mCommandParameters.poll();
                final float y1 = this.mCommandParameters.poll();
                final float x2 = this.mCommandParameters.poll();
                final float y2 = this.mCommandParameters.poll();
                final float x = this.mCommandParameters.poll();
                final float y = this.mCommandParameters.poll();
                this.mPath.cubicTo(x1, y1, x2, y2, x, y);
                this.mLastCubicBezierX2 = x2;
                this.mLastCubicBezierY2 = y2;
                this.mLastX = x;
                this.mLastY = y;
            }
        } else {
            while (this.mCommandParameters.size() >= 6) {
                final float x1 = this.mCommandParameters.poll() + this.mLastX;
                final float y1 = this.mCommandParameters.poll() + this.mLastY;
                final float x2 = this.mCommandParameters.poll() + this.mLastX;
                final float y2 = this.mCommandParameters.poll() + this.mLastY;
                final float x = this.mCommandParameters.poll() + this.mLastX;
                final float y = this.mCommandParameters.poll() + this.mLastY;
                this.mPath.cubicTo(x1, y1, x2, y2, x, y);
                this.mLastCubicBezierX2 = x2;
                this.mLastCubicBezierY2 = y2;
                this.mLastX = x;
                this.mLastY = y;
            }
        }
    }

    private void generateSmoothCubicBezierCurve(final boolean pAbsolute) {
        this.assertParameterCountMinimum(4);
        /** Draws a cubic bezier curve from the last point to x,y.
         * x2,y2 is the end control point.
         * The start control point is is assumed to be the same as
         * the end control point of the previous curve. */
        if (pAbsolute) {
            while (this.mCommandParameters.size() >= 4) {
                final float x1 = 2 * this.mLastX - this.mLastCubicBezierX2;
                final float y1 = 2 * this.mLastY - this.mLastCubicBezierY2;
                final float x2 = this.mCommandParameters.poll();
                final float y2 = this.mCommandParameters.poll();
                final float x = this.mCommandParameters.poll();
                final float y = this.mCommandParameters.poll();
                this.mPath.cubicTo(x1, y1, x2, y2, x, y);
                this.mLastCubicBezierX2 = x2;
                this.mLastCubicBezierY2 = y2;
                this.mLastX = x;
                this.mLastY = y;
            }
        } else {
            while (this.mCommandParameters.size() >= 4) {
                final float x1 = 2 * this.mLastX - this.mLastCubicBezierX2;
                final float y1 = 2 * this.mLastY - this.mLastCubicBezierY2;
                final float x2 = this.mCommandParameters.poll() + this.mLastX;
                final float y2 = this.mCommandParameters.poll() + this.mLastY;
                final float x = this.mCommandParameters.poll() + this.mLastX;
                final float y = this.mCommandParameters.poll() + this.mLastY;
                this.mPath.cubicTo(x1, y1, x2, y2, x, y);
                this.mLastCubicBezierX2 = x2;
                this.mLastCubicBezierY2 = y2;
                this.mLastX = x;
                this.mLastY = y;
            }
        }
    }

    private void generateQuadraticBezierCurve(final boolean pAbsolute) {
        this.assertParameterCountMinimum(4);
        /** Draws a quadratic bezier curve from mLastX,mLastY x,y. x1,y1 is the control point.. */
        if (pAbsolute) {
            while (this.mCommandParameters.size() >= 4) {
                final float x1 = this.mCommandParameters.poll();
                final float y1 = this.mCommandParameters.poll();
                final float x2 = this.mCommandParameters.poll();
                final float y2 = this.mCommandParameters.poll();
                this.mPath.quadTo(x1, y1, x2, y2);
                this.mLastQuadraticBezierX2 = x2;
                this.mLastQuadraticBezierY2 = y2;
                this.mLastX = x2;
                this.mLastY = y2;
            }
        } else {
            while (this.mCommandParameters.size() >= 4) {
                final float x1 = this.mCommandParameters.poll() + this.mLastX;
                final float y1 = this.mCommandParameters.poll() + this.mLastY;
                final float x2 = this.mCommandParameters.poll() + this.mLastX;
                final float y2 = this.mCommandParameters.poll() + this.mLastY;
                this.mPath.quadTo(x1, y1, x2, y2);
                this.mLastQuadraticBezierX2 = x2;
                this.mLastQuadraticBezierY2 = y2;
                this.mLastX = x2;
                this.mLastY = y2;
            }
        }
    }

    private void generateSmoothQuadraticBezierCurve(final boolean pAbsolute) {
        this.assertParameterCountMinimum(2);
        /** Draws a quadratic bezier curve from mLastX,mLastY to x,y.
         * The control point is assumed to be the same as the last control point used. */
        if (pAbsolute) {
            while (this.mCommandParameters.size() >= 2) {
                final float x1 = 2 * this.mLastX - this.mLastQuadraticBezierX2;
                final float y1 = 2 * this.mLastY - this.mLastQuadraticBezierY2;
                final float x2 = this.mCommandParameters.poll();
                final float y2 = this.mCommandParameters.poll();
                this.mPath.quadTo(x1, y1, x2, y2);
                this.mLastQuadraticBezierX2 = x2;
                this.mLastQuadraticBezierY2 = y2;
                this.mLastX = x2;
                this.mLastY = y2;
            }
        } else {
            while (this.mCommandParameters.size() >= 2) {
                final float x1 = 2 * this.mLastX - this.mLastQuadraticBezierX2;
                final float y1 = 2 * this.mLastY - this.mLastQuadraticBezierY2;
                final float x2 = this.mCommandParameters.poll() + this.mLastX;
                final float y2 = this.mCommandParameters.poll() + this.mLastY;
                this.mPath.quadTo(x1, y1, x2, y2);
                this.mLastQuadraticBezierX2 = x2;
                this.mLastQuadraticBezierY2 = y2;
                this.mLastX = x2;
                this.mLastY = y2;
            }
        }
    }

    private void generateArc(final boolean pAbsolute) {
        this.assertParameterCountMinimum(7);
        if (pAbsolute) {
            while (this.mCommandParameters.size() >= 7) {
                final float rx = this.mCommandParameters.poll();
                final float ry = this.mCommandParameters.poll();
                final float theta = this.mCommandParameters.poll();
                final boolean largeArcFlag = this.mCommandParameters.poll().intValue() == 1;
                final boolean sweepFlag = this.mCommandParameters.poll().intValue() == 1;
                final float x = this.mCommandParameters.poll();
                final float y = this.mCommandParameters.poll();

                this.generateArc(rx, ry, theta, largeArcFlag, sweepFlag, x, y);

                this.mLastX = x;
                this.mLastY = y;
            }
        } else {
            while (this.mCommandParameters.size() >= 7) {
                final float rx = this.mCommandParameters.poll();
                final float ry = this.mCommandParameters.poll();
                final float theta = this.mCommandParameters.poll();
                final boolean largeArcFlag = this.mCommandParameters.poll().intValue() == 1;
                final boolean sweepFlag = this.mCommandParameters.poll().intValue() == 1;
                final float x = this.mCommandParameters.poll() + this.mLastX;
                final float y = this.mCommandParameters.poll() + this.mLastY;

                this.generateArc(rx, ry, theta, largeArcFlag, sweepFlag, x, y);

                this.mLastX = x;
                this.mLastY = y;
            }
        }
    }

    /**
     * Based on: org.apache.batik.ext.awt.geom.ExtendedGeneralPath.computeArc(...)
     *
     * @see <a href="http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter">http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter</a>
     */
    private void generateArc(final float rx, final float ry, final float pTheta, final boolean pLargeArcFlag, final boolean pSweepFlag, final float pX, final float pY) {
        /* Compute the half distance between the current and the end point. */
        final float dx = (this.mLastX - pX) * 0.5f;
        final float dy = (this.mLastY - pY) * 0.5f;

		/* Convert theta to radians. */
        final float thetaRad = MathUtils.degToRad(pTheta % 360f);
        final float cosAngle = FloatMath.cos(thetaRad);
        final float sinAngle = FloatMath.sin(thetaRad);

		/* Step 1 : Compute (x1, y1) */
        final float x1 = (cosAngle * dx + sinAngle * dy);
        final float y1 = (-sinAngle * dx + cosAngle * dy);

		/* Ensure radii are large enough. */
        float radiusX = Math.abs(rx);
        float radiusY = Math.abs(ry);
        float Prx = radiusX * radiusX;
        float Pry = radiusY * radiusY;
        final float Px1 = x1 * x1;
        final float Py1 = y1 * y1;
        /* Check that radii are large enough. */
        final float radiiCheck = Px1 / Prx + Py1 / Pry;
        if (radiiCheck > 1) {
            radiusX = FloatMath.sqrt(radiiCheck) * radiusX;
            radiusY = FloatMath.sqrt(radiiCheck) * radiusY;
            Prx = radiusX * radiusX;
            Pry = radiusY * radiusY;
        }

		/* Step 2 : Compute (cx_dash, cy_dash) */
        float sign = (pLargeArcFlag == pSweepFlag) ? -1 : 1;
        float sq = ((Prx * Pry) - (Prx * Py1) - (Pry * Px1)) / ((Prx * Py1) + (Pry * Px1));
        sq = (sq < 0) ? 0 : sq;
        final float coef = sign * FloatMath.sqrt(sq);
        final float cx_dash = coef * ((radiusX * y1) / radiusY);
        final float cy_dash = coef * -((radiusY * x1) / radiusX);

        //- Step 3 : Compute (cx, cy) from (cx_dash, cy_dash) */
        final float cx = ((this.mLastX + pX) * 0.5f) + (cosAngle * cx_dash - sinAngle * cy_dash);
        final float cy = ((this.mLastY + pY) * 0.5f) + (sinAngle * cx_dash + cosAngle * cy_dash);

		/* Step 4 : Compute the angleStart (angle1) and the sweepAngle (dangle). */
        final float ux = (x1 - cx_dash) / radiusX;
        final float uy = (y1 - cy_dash) / radiusY;
        final float vx = (-x1 - cx_dash) / radiusX;
        final float vy = (-y1 - cy_dash) / radiusY;

		/* Compute the startAngle. */
        float p = ux; // (1 * ux) + (0 * uy)
        float n = FloatMath.sqrt((ux * ux) + (uy * uy));
        sign = (uy < 0) ? -1f : 1f;
        float startAngle = MathUtils.radToDeg(sign * (float) Math.acos(p / n));

		/* Compute the sweepAngle. */
        n = FloatMath.sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy));
        p = ux * vx + uy * vy;
        sign = (ux * vy - uy * vx < 0) ? -1f : 1f;
        float sweepAngle = MathUtils.radToDeg(sign * (float) Math.acos(p / n));
        if (!pSweepFlag && sweepAngle > 0) {
            sweepAngle -= 360f;
        } else if (pSweepFlag && sweepAngle < 0) {
            sweepAngle += 360f;
        }
        sweepAngle %= 360f;
        startAngle %= 360f;

		/* Generate bounding rect. */
        final float left = cx - radiusX;
        final float top = cy - radiusY;
        final float right = cx + radiusX;
        final float bottom = cy + radiusY;
        this.mArcRect.set(left, top, right, bottom);

		/* Append the arc to the path. */
        this.mPath.arcTo(this.mArcRect, startAngle, sweepAngle);
    }

    private void generateClose() {
        this.assertParameterCount(0);
        this.mPath.close();
        this.mLastX = this.mSubPathStartX;
        this.mLastY = this.mSubPathStartY;
    }

    // ===========================================================
    // Inner and Anonymous Classes
    // ===========================================================

    /**
     * Parses numbers from SVG text. Based on the Batik Number Parser (Apache 2 License).
     *
     * @author Apache Software Foundation
     * @author Larva Labs LLC
     *         (c) 2010 Nicolas Gramlich
     *         (c) 2011 Zynga Inc.
     * @author Nicolas Gramlich
     */
    public class PathParserHelper {
        // ===========================================================
        // Constants
        // ===========================================================

        // ===========================================================
        // Fields
        // ===========================================================

        // ===========================================================
        // Constructors
        // ===========================================================

        // ===========================================================
        // Getter & Setter
        // ===========================================================

        // ===========================================================
        // Methods for/from SuperClass/Interfaces
        // ===========================================================

        // ===========================================================
        // Methods
        // ===========================================================

        private char read() {
            if (SVGPathParser.this.mPosition < SVGPathParser.this.mLength) {
                SVGPathParser.this.mPosition++;
            }
            if (SVGPathParser.this.mPosition == SVGPathParser.this.mLength) {
                return '\0';
            } else {
                return SVGPathParser.this.mString.charAt(SVGPathParser.this.mPosition);
            }
        }

        public void skipWhitespace() {
            while (SVGPathParser.this.mPosition < SVGPathParser.this.mLength) {
                if (Character.isWhitespace(SVGPathParser.this.mString.charAt(SVGPathParser.this.mPosition))) {
                    this.advance();
                } else {
                    break;
                }
            }
        }

        public void skipNumberSeparator() {
            while (SVGPathParser.this.mPosition < SVGPathParser.this.mLength) {
                final char c = SVGPathParser.this.mString.charAt(SVGPathParser.this.mPosition);
                switch (c) {
                    case ' ':
                    case ',':
                    case '\n':
                    case '\t':
                        this.advance();
                        break;
                    default:
                        return;
                }
            }
        }

        public void advance() {
            SVGPathParser.this.mCurrentChar = this.read();
        }

        /**
         * Parses the content of the buffer and converts it to a float.
         */
        private float parseFloat() {
            int mantissa = 0;
            int mantissaDigit = 0;
            boolean mantPosition = true;
            boolean mantissaRead = false;

            int exp = 0;
            int expDig = 0;
            int expAdj = 0;
            boolean expPos = true;

            switch (SVGPathParser.this.mCurrentChar) {
                case '-':
                    mantPosition = false;
                case '+':
                    SVGPathParser.this.mCurrentChar = this.read();
            }

            m1:
            switch (SVGPathParser.this.mCurrentChar) {
                default:
                    return Float.NaN;

                case '.':
                    break;

                case '0':
                    mantissaRead = true;
                    l:
                    for (; ; ) {
                        SVGPathParser.this.mCurrentChar = this.read();
                        switch (SVGPathParser.this.mCurrentChar) {
                            case '1':
                            case '2':
                            case '3':
                            case '4':
                            case '5':
                            case '6':
                            case '7':
                            case '8':
                            case '9':
                                break l;
                            case '.':
                            case 'e':
                            case 'E':
                                break m1;
                            default:
                                return 0.0f;
                            case '0':
                        }
                    }

                case '1':
                case '2':
                case '3':
                case '4':
                case '5':
                case '6':
                case '7':
                case '8':
                case '9':
                    mantissaRead = true;
                    l:
                    for (; ; ) {
                        if (mantissaDigit < 9) {
                            mantissaDigit++;
                            mantissa = mantissa * 10 + (SVGPathParser.this.mCurrentChar - '0');
                        } else {
                            expAdj++;
                        }
                        SVGPathParser.this.mCurrentChar = this.read();
                        switch (SVGPathParser.this.mCurrentChar) {
                            default:
                                break l;
                            case '0':
                            case '1':
                            case '2':
                            case '3':
                            case '4':
                            case '5':
                            case '6':
                            case '7':
                            case '8':
                            case '9':
                        }
                    }
            }

            if (SVGPathParser.this.mCurrentChar == '.') {
                SVGPathParser.this.mCurrentChar = this.read();
                m2:
                switch (SVGPathParser.this.mCurrentChar) {
                    default:
                    case 'e':
                    case 'E':
                        if (!mantissaRead) {
                            throw new IllegalArgumentException("Unexpected char '" + SVGPathParser.this.mCurrentChar + "'.");
                        }
                        break;

                    case '0':
                        if (mantissaDigit == 0) {
                            l:
                            for (; ; ) {
                                SVGPathParser.this.mCurrentChar = this.read();
                                expAdj--;
                                switch (SVGPathParser.this.mCurrentChar) {
                                    case '1':
                                    case '2':
                                    case '3':
                                    case '4':
                                    case '5':
                                    case '6':
                                    case '7':
                                    case '8':
                                    case '9':
                                        break l;
                                    default:
                                        if (!mantissaRead) {
                                            return 0.0f;
                                        }
                                        break m2;
                                    case '0':
                                }
                            }
                        }
                    case '1':
                    case '2':
                    case '3':
                    case '4':
                    case '5':
                    case '6':
                    case '7':
                    case '8':
                    case '9':
                        l:
                        for (; ; ) {
                            if (mantissaDigit < 9) {
                                mantissaDigit++;
                                mantissa = mantissa * 10 + (SVGPathParser.this.mCurrentChar - '0');
                                expAdj--;
                            }
                            SVGPathParser.this.mCurrentChar = this.read();
                            switch (SVGPathParser.this.mCurrentChar) {
                                default:
                                    break l;
                                case '0':
                                case '1':
                                case '2':
                                case '3':
                                case '4':
                                case '5':
                                case '6':
                                case '7':
                                case '8':
                                case '9':
                            }
                        }
                }
            }

            switch (SVGPathParser.this.mCurrentChar) {
                case 'e':
                case 'E':
                    SVGPathParser.this.mCurrentChar = this.read();
                    switch (SVGPathParser.this.mCurrentChar) {
                        default:
                            throw new IllegalArgumentException("Unexpected char '" + SVGPathParser.this.mCurrentChar + "'.");
                        case '-':
                            expPos = false;
                        case '+':
                            SVGPathParser.this.mCurrentChar = this.read();
                            switch (SVGPathParser.this.mCurrentChar) {
                                default:
                                    throw new IllegalArgumentException("Unexpected char '" + SVGPathParser.this.mCurrentChar + "'.");
                                case '0':
                                case '1':
                                case '2':
                                case '3':
                                case '4':
                                case '5':
                                case '6':
                                case '7':
                                case '8':
                                case '9':
                            }
                        case '0':
                        case '1':
                        case '2':
                        case '3':
                        case '4':
                        case '5':
                        case '6':
                        case '7':
                        case '8':
                        case '9':
                    }

                    en:
                    switch (SVGPathParser.this.mCurrentChar) {
                        case '0':
                            l:
                            for (; ; ) {
                                SVGPathParser.this.mCurrentChar = this.read();
                                switch (SVGPathParser.this.mCurrentChar) {
                                    case '1':
                                    case '2':
                                    case '3':
                                    case '4':
                                    case '5':
                                    case '6':
                                    case '7':
                                    case '8':
                                    case '9':
                                        break l;
                                    default:
                                        break en;
                                    case '0':
                                }
                            }

                        case '1':
                        case '2':
                        case '3':
                        case '4':
                        case '5':
                        case '6':
                        case '7':
                        case '8':
                        case '9':
                            l:
                            for (; ; ) {
                                if (expDig < 3) {
                                    expDig++;
                                    exp = exp * 10 + (SVGPathParser.this.mCurrentChar - '0');
                                }
                                SVGPathParser.this.mCurrentChar = this.read();
                                switch (SVGPathParser.this.mCurrentChar) {
                                    default:
                                        break l;
                                    case '0':
                                    case '1':
                                    case '2':
                                    case '3':
                                    case '4':
                                    case '5':
                                    case '6':
                                    case '7':
                                    case '8':
                                    case '9':
                                }
                            }
                    }
                default:
            }

            if (!expPos) {
                exp = -exp;
            }
            exp += expAdj;
            if (!mantPosition) {
                mantissa = -mantissa;
            }

            return this.buildFloat(mantissa, exp);
        }

        public float nextFloat() {
            this.skipWhitespace();
            final float f = this.parseFloat();
            this.skipNumberSeparator();
            return f;
        }

        public float buildFloat(int pMantissa, final int pExponent) {
            if (pExponent < -125 || pMantissa == 0) {
                return 0.0f;
            }

            if (pExponent >= 128) {
                return (pMantissa > 0)
                        ? Float.POSITIVE_INFINITY
                        : Float.NEGATIVE_INFINITY;
            }

            if (pExponent == 0) {
                return pMantissa;
            }

            if (pMantissa >= (1 << 26)) {
                pMantissa++;  // round up trailing bits if they will be dropped.
            }

            return (float) ((pExponent > 0) ? pMantissa * org.andengine.extension.svg.util.constants.MathUtils.POWERS_OF_10[pExponent] : pMantissa / org.andengine.extension.svg.util.constants.MathUtils.POWERS_OF_10[-pExponent]);
        }

        // ===========================================================
        // Inner and Anonymous Classes
        // ===========================================================
    }
}
